跳到主要内容

SpringBoot 整合 WebClient

WebClient 是什么

官网文档

WebClient 是 Spring5 引入的异步非阻塞的响应式的网络框架(用于代替上面的 RestTemplate)

他的执行流程是先创建个 webclient.create() 实例,之后调用 get(), post() 等调用方式, uri() 指定路径, retrieve() 用来发起请求并获得响应,bodyToMono(String.class) 用来指定请求结果需要处理为 String,并包装为 Reactor 的 Mono 对象

// 直接创建
WebClient webClient = WebClient.create();
// 链式调用
Mono<String> mono = webClient.get().uri("https://www.baidu.com").retrieve().bodyToMono(String.class);
//
mono.subscribe(System.out::println);



// 使用建造模式创建
WebClient webClient1 = WebClient.builder()
.baseUrl("https://api.github.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
.build();

GET、POST 请求

GET 请求

Mono<String> resp = WebClient.create()
.method(HttpMethod.GET)
.uri("http://www.baidu.com")
.cookie("token","xxxx")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.retrieve().bodyToMono(String.class);

POST 请求

可以使用 BodyInserters 类提供的各种工厂方法来构造 BodyInserter 对象并将其传递给 body 方法。BodyInserters 类包含从Object、Publisher、Resource、FormData、MultipartData 等创建 BodyInserter 的方法。

 @Test
public void testFormParam(){
MultiValueMap<String, String> formData = new LinkedMultiValueMap();
formData.add("name1","value1");
formData.add("name2","value2");
Mono<String> resp = WebClient.create().post()
.uri("http://www.w3school.com.cn/test/demo_form.asp")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(formData))
.retrieve().bodyToMono(String.class);
System.out.print("result:" + resp.block());
}

发送 JSON

static class Book {
String name;
String title;
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}
}

@Test
public void testPostJson(){
Book book = new Book();
book.setName("name");
book.setTitle("this is title");
Mono<String> resp = WebClient.create().post()
.uri("http://localhost:8080/demo/json")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(book),Book.class)
.retrieve().bodyToMono(String.class);
LOGGER.info("result:{}",resp.block());
}

异常处理

可以使用 onStatus 根据 status code 进行异常适配

可以使用 doOnError 异常适配

可以使用 onErrorReturn 返回默认值

 @Test
public void testFormParam4xx(){
WebClient webClient = WebClient.builder()
.baseUrl("https://api.github.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
.build();

WebClient.ResponseSpec responseSpec = webClient.method(HttpMethod.GET)
.uri("/user/repos?sort={sortField}&direction={sortDirection}",
"updated", "desc")
.retrieve();

Mono<String> mono = responseSpec
.onStatus(e -> e.is4xxClientError(),resp -> {
log.error("error:{},msg:{}",resp.statusCode().value(),resp.statusCode().getReasonPhrase());
return Mono.error(new RuntimeException(resp.statusCode().value() + " : " + resp.statusCode().getReasonPhrase()));
})
.bodyToMono(String.class)
.doOnError(WebClientResponseException.class, err -> {
log.info("ERROR status:{},msg:{}",err.getRawStatusCode(),err.getResponseBodyAsString());
throw new RuntimeException(err.getMessage());
})
.onErrorReturn("fallback");

String result = mono.block();
System.out.print(result);
}

响应结果

retrieve 方法是直接获取响应 body,但是,如果需要响应的 头信息、Cookie 等,可以使用 exchange 方法,该方法可以访问整个 ClientResponse。由于响应的得到是异步的,所以都可以调用 block 方法来阻塞当前程序,等待获得响应的结果。

String baseUrl = "http://localhost:8081";
WebClient webClient = WebClient.create(baseUrl);

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", "u123");
map.add("password", "p123");

Mono<ClientResponse> mono = webClient.post().uri("login").syncBody(map).exchange();
ClientResponse response = mono.block();
if (response.statusCode() == HttpStatus.OK) {
Mono<Result> resultMono = response.bodyToMono(Result.class);
resultMono.subscribe(result -> {
if (result.isSuccess()) {
ResponseCookie sidCookie = response.cookies().getFirst("sid");
Flux<User> userFlux = webClient.get().uri("users").cookie(sidCookie.getName(), sidCookie.getValue()).retrieve().bodyToFlux(User.class);
userFlux.subscribe(System.out::println);
}
});
}

TODO: 待更新...